package com.heima.article.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.heima.article.mapper.ApArticleContentMapper;
import com.heima.article.service.ApArticleService;
import com.heima.article.service.ArticleFreemarkerService;
import com.heima.common.constants.ArticleConstants;
import com.heima.file.service.FileStorageService;
import com.heima.model.article.pojos.ApArticle;
import com.heima.model.search.vos.SearchArticleVo;
import freemarker.template.Configuration;
import freemarker.template.Template;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;

@Service
@Slf4j
@Transactional//因为涉及到多张表的操作，所以需要事务
public class ArticleFreemarkerServiceImpl implements ArticleFreemarkerService {

	@Autowired
	private ApArticleContentMapper apArticleContentMapper;

	@Autowired
	private Configuration configuration;

	@Autowired
	private FileStorageService fileStorageService;

	@Autowired
	private ApArticleService apArticleService;

	/**
	 * 生成静态文件上传到minIO中
	 * @param apArticle
	 * @param content
	 */
	@Async//异步调用，防止阻塞，提高响应速度
	@Override
	public void buildArticleToMinIO(ApArticle apArticle, String content) {
		//已知文章的id
		//4.1 获取文章内容
		if(StringUtils.isNotBlank(content)){
			//4.2 文章内容通过freemarker生成html文件
			Template template = null;
			StringWriter out = new StringWriter();
			try {
				//从configuration对象中获取名为article.ftl的Freemarker模板
				template = configuration.getTemplate("article.ftl");
				//数据模型，创建一个Map对象存储数据模型，将文章内容（已转换为JSONArray）放入数据模型中，键名为"content"
				Map<String,Object> contentDataModel = new HashMap<>();
				contentDataModel.put("content", JSONArray.parseArray(content));
				//合成，使用Freemarker模板引擎将数据模型与模板合成，生成HTML内容，并将结果写入StringWriter对象out中
				template.process(contentDataModel,out);
			} catch (Exception e) {
				//捕获并打印可能抛出的异常信息，以便于调试和错误排查
				e.printStackTrace();
			}

			//4.3 把html文件上传到minio中
			//将StringWriter中的HTML内容转换为字节数组
			//再从字节数组中创建一个ByteArrayInputStream对象，用于后续的文件上传
			InputStream in = new ByteArrayInputStream(out.toString().getBytes());
			//将生成的HTML文件上传到MinIO存储系统中。文件名由文章ID和.html后缀组成，返回存储路径path
			String path = fileStorageService.uploadHtmlFile("", apArticle.getId() + ".html", in);


			//4.4 修改ap_article表，保存static_url字段
			apArticleService.update(Wrappers.<ApArticle>lambdaUpdate().eq(ApArticle::getId,apArticle.getId())
					.set(ApArticle::getStaticUrl,path));

			//发送消息，创建索引，这里封装了一个方法用来给minio生成模板之后，执行给文章创建索引
			//此时，文章模块是生产者的角色，将消息发送到kafka，被消费者监听
			createArticleESIndex(apArticle,content,path);
		}
	}

	//注入kafka模板类，用于简化kafka操作
	@Autowired
	//从Spring容器中获取 KafkaTemplate 对象并赋值给 kafkaTemplate 变量
	//KafkaTemplate简化了与Kafka消息队列的交互;
	//这里的类型参数 <String,String> 表示发送的消息的键和值都是字符串类型
	private KafkaTemplate<String,String> kafkaTemplate;

	/**
	 * 发送消息，创建文章的索引
	 * @param apArticle
	 * @param content
	 * @param path
	 */
	//这三个参数分别表示文章对象、文章内容和静态文件的存储路径
	private void createArticleESIndex(ApArticle apArticle, String content, String path) {
		//创建了一个新的 SearchArticleVo 对象 vo，用于存储将要发送到Elasticsearch的索引信息
		SearchArticleVo vo = new SearchArticleVo();
		//目的是为了将 ApArticle 对象中的所有属性（如标题、作者等）复制到 SearchArticleVo 对象中，以便后续处理
		BeanUtils.copyProperties(apArticle,vo);
		//设置了 SearchArticleVo 对象的内容（content）和静态文件的URL（path）
		//content 是文章的具体内容，path 是文章静态HTML文件在MinIO存储系统中的路径
		//因为ApArticle类中没有这两个属性，所以需要额外给SearchArticleVo设置这两个属性
		vo.setContent(content);
		vo.setStaticUrl(path);

		//调用kafkaTemplate.send 方法用于向Kafka发送消息
		//ArticleConstants.ARTICLE_ES_SYNC_TOPIC 是一个主题名称，被定义为了常量，方便复用
		//JSON.toJSONString(vo) 将 SearchArticleVo 对象转换为JSON格式的字符串，以便Kafka能够传输这个对象的数据
		kafkaTemplate.send(ArticleConstants.ARTICLE_ES_SYNC_TOPIC, JSON.toJSONString(vo));
	}

}
